System V的ABI的Function Calling Sequence(函数调用顺序)
1. Register and Stack Frame(寄存器和帧栈)
AMD64体系提供16个64位通用寄存器。此外,该体系还提供16个SSE寄存器(每一个占128位)以及8个X87 floating point register(浮点寄存器,每一个占80位)。每一个X87浮点寄存器在MMX/3DNow! 模型中也许代表一个64位寄存器。这些寄存器相对于一个正在运行的程序中的所有procedures而言都是全局的。
%rbp
,%rbx
以及%r12
到%r15
都属于calling function(调用函数),被调用的函数需要保存这些值。换言之,一个被调用的函数必须为caller(调用者)这些寄存器的值。剩下的寄存器属于被调用函数。如果一个调用函数想要通过函数调用来保存这类寄存器的值,它必须在其local stack frame(局部帧栈)中保存这一值。
2. The Stack Frame(帧栈)
除了寄存器之外,每一个函数还用一个run-time stack(运行栈)。该栈从高地址向下增长,图示为stack organization:
input arrgument area(输入参数区)的结尾应该与16位的边界进行对齐。换言之,当control被转换为function entry point(函数进入点)时,栈指针%rsp总是指向最近分配的帧栈的尾部。
Entry point
在CS中,entry point是控制从OS转换为计算机程序的地方,处理器从此处加载程序或者代码段并开始执行。在一些OS或编程语言中,initial entry不属于程序的一部分,而属于runtime library(运行时库),在这种情况下,程序在第一次载入的时,在做其他事之前首次会调用运行时库。当运行库返回时,实际的代码才会开始执行。这标志着从load time(装载期,或者动态链接期)向run time(运行期)的转变。
Parameter Passing(参数传递)
当参数值被计算之后,它们便被放置在寄存器中,或者被压入栈中。
定义:定义一些classes来classify arguments(区分参数)。这些classes对应于AMD64寄存器:
- INTEGER
这个class包含了符合通用寄存器中的integral types(整型) - SSE
这个class包含了符合SSE寄存器的类型 - SSEUP
The class consists of types that fit into a SSE register and can be passed and returned in the most significant half of it.(这个class包含了满足SSE寄存器的类型,而且它可以以半个最重要的寄存器传递和返回) - X87,X87UP
这些classes包含通过x87 FPU(Floating Point Unit)返回的类型 - COMPLEX_X87
这个class包含了经由x87 FPU返回的类型 - NO_CLASS
此class被用来在算法中起初始化的作用。它将被用来进行padding(填充)和empty structure and unions(空结构体和空共用体) - MEMORY
该class包含了在内存中经由stack传递和返回的类型
分类
每一个参数的大小都会被填充为8bit的整数倍
- INTEGER class(包含signed和unsigned两种)
_Bool
char
short
int
long
long long
pointer
- SSE class
float
double
_m64
_float128
和_m128
被分为两部分。The least significant ones belong to class SSE, the most significant one to class SSEUP.- 64位参数类型
long double
的小数部分属于X87 class,16位指数加上6 bits填充的类型属于X87UP class _int128
类型的参数提供同INTEGER一样的操作,但通用寄存器不适用于它们,而是需要两个寄存器。故可以将_int128
看成如下的实现:
typedef struct{
long low,high;
}_int128;
【注意】:除了存储在内存中且必须在16字节的边界上对齐的__int128
类型参数外。
- aggregate(structure and array,聚合类型)和union types:
- 如果对象的大小大于两个8字节,或者为C++中的non-POD structure或union,或者包含unaligned fields(未对齐的域),则以上类型属于MEMERY class
- 这两个8字节都将被初始化为NO_CLASS class
- 对象的每一个field(字段)都将进行递归地分类,以便总是考虑两个fields。resulting class按照8字节的field进行计算:
(1) 如果两个classes相等, 则此class便是resulting class
(2)如果一个为NO_CLASS,则resulting class便是另一个class
(3)如果一个为MEMORY,则结果为MEMORY class
(4)如果一个为INTEGER,则结果为INTEGER
(5)如果一个为X87,X87UP class,则结果为MEMORY
(6)其他class使用SSE
- 然后进行post merger cleanup(合并后清理)
(1)如果一个class为MEMORY,则整个参数在内存中进行传递
(2)如果SSEUP在SSE之后,则SSEUP将被转化为SSE
Passing
一旦参数被分类,则寄存器便被分配(按照从左到右的顺序),如下所示:
- 如果class是MEMORY,则将参数传递给stack
- 如果class为INTEGER,则下一个可用寄存器序列为
%rdi
、%rsi
、%rdx
、%rcx
、%r8
以及%r9
- 如果class为SSE,则下一个可用的SSE寄存器将被使用,顺序为从
%xmm0
到%xmm7
- 如果class为SSEUP,则将8字节传递给最近使用的SSE寄存器的高半地址部分
- 如果class为X87,X87UP,则在内存中进行传递
如果没有可用的寄存器用于8字节的参数,则整个参数将被传递给stack;如果已经给这个参数的某些8字节分配了寄存器,则这些分配将被reverted(还原)
一旦寄存器被分配,则在内存中进行传递的参数将以逆序(即从右到左的顺序)压入stack中
Returning of Values(返回值)
返回值是根据以下算法完成的:
- 利用classification algorithm(分类算法)对返回值进行区分
- 如果类型拥有MEMORY class,则caller(调用者)为返回值提供空间,并且将此存储空间作为函数的第一个参数传递给
%rdi
。实际上,这个地址变成了“隐藏的”第一参数。返回时,%rax
将包含caller在%rdi
中传入的地址 - 如果class为INTEGER,则下一个可用的寄存器顺序
%rax
、%rdx
将被使用 - 如果class为SSE,则下个可用的寄存器序
%xmm0
、%xmm1
将被使用 - 如果class为SSEUP,则将8字节传递给最近使用的SSE寄存器的高半地址部分
- 如果class为X87,则在X87 stack的
%st0
处以80bits大小X87 number的形式返回该值 - 如果class为X87UP,则该值和前一个X87值在
%st0
处一同返回 - 如果class为COMPLEX_87,则该值的real part(实数部分)在
%st0
处返回,imaginary part(虚数部分)在%st1
处返回